#include <sdk.h>
#include "cdb_driver.h"
#include "cdb_commands.h"
#include "debuggeroptionsdlg.h"
#include <M_Code_debugger_interfaces.h>

static wxRegEx rePrompt( _T( "([0-9]+:){1,2}[0-9]+(:x86)?>" ) );
static wxRegEx reBP( _T( "Breakpoint ([0-9]+) hit" ) );
static wxRegEx reFile( _T( "[ \t]([A-z]+.*)[ \t]+\\[([A-z]:)(.*) @ ([0-9]+)\\]" ) );

CDB_driver::CDB_driver( DebuggerGDB* plugin )
  : DebuggerDriver( plugin ),
    m_Target( nullptr ),
    m_IsStarted( false ) {
}

CDB_driver::~CDB_driver() {
}

wxString CDB_driver::GetCommonCommandLine( const wxString& debugger ) {
  wxString cmd;
  cmd << debugger;
  cmd << _T( " -G" );
  cmd << _T( " -lines" );
  if( m_Target->GetTargetType() == ttConsoleOnly ) {
    cmd << _T( " -2" );
  }
  if( m_Dirs.GetCount() > 0 ) {
    cmd << _T( " -y " );
    for( unsigned int i = 0; i < m_Dirs.GetCount(); ++i ) {
      cmd << m_Dirs[i] << wxPATH_SEP;
    }
    cmd << _T( " -srcpath " );
    for( unsigned int i = 0; i < m_Dirs.GetCount(); ++i ) {
      cmd << m_Dirs[i] << wxPATH_SEP;
    }
  }
  return cmd;
}


wxString CDB_driver::GetCommandLine( const wxString& debugger, const wxString& debuggee, M_Code_unused const wxString &userArguments ) {
  wxString cmd = GetCommonCommandLine( debugger );
  cmd << _T( ' ' );
  wxFileName debuggeeFileName( debuggee );
  if( debuggeeFileName.IsAbsolute() ) {
    cmd << debuggee;
  } else {
    cmd << m_Target->GetParentProject()->GetBasePath() << _T( '/' ) << debuggee;
  }
  return cmd;
}

wxString CDB_driver::GetCommandLine( const wxString& debugger, int pid, M_Code_unused const wxString &userArguments ) {
  wxString cmd = GetCommonCommandLine( debugger );
  cmd << _T( " -p " ) << F( _T( "%d" ), pid );
  return cmd;
}

void CDB_driver::SetTarget( ProjectBuildTarget* target ) {
  m_Target = target;
}

void CDB_driver::Prepare( M_Code_unused bool isConsole, M_Code_unused int printElements ) {
  m_QueueBusy = true;
  QueueCommand( new DebuggerCmd( this, _T( ".echo Clear buffer" ) ), High );
  QueueCommand( new CdbCmd_GetPID( this ) );
}

void CDB_driver::Start( M_Code_unused bool breakOnEntry ) {
  QueueCommand( new DebuggerCmd( this, _T( "l+t" ) ) );
  QueueCommand( new DebuggerCmd( this, _T( "l+s" ) ) );
  QueueCommand( new DebuggerCmd( this, _T( "l+o" ) ) );
  if( !m_pDBG->GetActiveConfigEx().GetFlag( DebuggerConfiguration::DoNotRun ) ) {
    QueueCommand( new CdbCmd_Continue( this ) );
    m_IsStarted = true;
  }
}

void CDB_driver::Stop() {
  ResetCursor();
  QueueCommand( new DebuggerCmd( this, _T( "q" ) ) );
  m_IsStarted = false;
}

void CDB_driver::Continue() {
  ResetCursor();
  QueueCommand( new CdbCmd_Continue( this ) );
  m_IsStarted = true;
}

void CDB_driver::Step() {
  ResetCursor();
  QueueCommand( new DebuggerContinueBaseCmd( this, _T( "p" ) ) );
  QueueCommand( new CdbCmd_SwitchFrame( this, -1 ) );
}

void CDB_driver::StepInstruction() {
  NOT_IMPLEMENTED();
}

void CDB_driver::StepIntoInstruction() {
  NOT_IMPLEMENTED();
}

void CDB_driver::StepIn() {
  ResetCursor();
  QueueCommand( new DebuggerContinueBaseCmd( this, _T( "t" ) ) );
  Step();
}

void CDB_driver::StepOut() {
  ResetCursor();
  QueueCommand( new DebuggerContinueBaseCmd( this, _T( "gu" ) ) );
  QueueCommand( new CdbCmd_SwitchFrame( this, -1 ) );
}

void CDB_driver::SetNextStatement( M_Code_unused const wxString& filename, M_Code_unused int line ) {
  NOT_IMPLEMENTED();
}

void CDB_driver::Backtrace() {
  DoBacktrace( false );
}

void CDB_driver::DoBacktrace( bool switchToFirst ) {
  if( Manager::Get()->GetDebuggerManager()->UpdateBacktrace() ) {
    QueueCommand( new CdbCmd_Backtrace( this, switchToFirst ) );
  }
}

void CDB_driver::Disassemble() {
  QueueCommand( new CdbCmd_DisassemblyInit( this ) );
}

void CDB_driver::CPURegisters() {
  QueueCommand( new CdbCmd_InfoRegisters( this ) );
}

void CDB_driver::SwitchToFrame( size_t number ) {
  ResetCursor();
  QueueCommand( new CdbCmd_SwitchFrame( this, number ) );
}

void CDB_driver::SetVarValue( M_Code_unused const wxString& var, M_Code_unused const wxString& value ) {
  NOT_IMPLEMENTED();
}

void CDB_driver::MemoryDump() {
  NOT_IMPLEMENTED();
}

void CDB_driver::RunningThreads() {
  NOT_IMPLEMENTED();
}

void CDB_driver::InfoFrame() {
  NOT_IMPLEMENTED();
}

void CDB_driver::InfoDLL() {
  NOT_IMPLEMENTED();
}

void CDB_driver::InfoFiles() {
  NOT_IMPLEMENTED();
}

void CDB_driver::InfoFPU() {
  NOT_IMPLEMENTED();
}

void CDB_driver::InfoSignals() {
  NOT_IMPLEMENTED();
}

void CDB_driver::EnableCatchingThrow( M_Code_unused bool enable ) {
  NOT_IMPLEMENTED();
}

void CDB_driver::AddBreakpoint( cb::shared_ptr<DebuggerBreakpoint> bp ) {
  QueueCommand( new CdbCmd_AddBreakpoint( this, bp ) );
}

void CDB_driver::RemoveBreakpoint( cb::shared_ptr<DebuggerBreakpoint> bp ) {
  QueueCommand( new CdbCmd_RemoveBreakpoint( this, bp ) );
}

void CDB_driver::EvaluateSymbol( const wxString& symbol, const wxRect& tipRect ) {
  QueueCommand( new CdbCmd_TooltipEvaluation( this, symbol, tipRect ) );
}

void CDB_driver::UpdateWatches( M_Code_unused cb::shared_ptr<GDBWatch> localsWatch,
                                M_Code_unused cb::shared_ptr<GDBWatch> funcArgsWatch,
                                WatchesContainer &watches ) {
  bool updateWatches = false;
  for( WatchesContainer::iterator it = watches.begin(); it != watches.end(); ++it ) {
    WatchesContainer::reference watch = *it;
    if( watch->IsAutoUpdateEnabled() ) {
      QueueCommand( new CdbCmd_Watch( this, *it ) );
      updateWatches = true;
    }
  }
  if( updateWatches ) {
    QueueCommand( new DbgCmd_UpdateWatchesTree( this ) );
  }
}

void CDB_driver::UpdateWatch( const cb::shared_ptr<GDBWatch> &watch ) {
  QueueCommand( new CdbCmd_Watch( this, watch ) );
  QueueCommand( new DbgCmd_UpdateWatchesTree( this ) );
}

void CDB_driver::UpdateWatchLocalsArgs( cb::shared_ptr<GDBWatch> const &watch, bool locals ) {
}

void CDB_driver::Attach( M_Code_unused int pid ) {
}

void CDB_driver::Detach() {
  QueueCommand( new CdbCmd_Detach( this ) );
}

void CDB_driver::ParseOutput( const wxString& output ) {
  m_Cursor.changed = false;
  static wxString buffer;
  buffer << output << _T( '\n' );
  m_pDBG->DebugLog( output );
  if( rePrompt.Matches( buffer ) ) {
    int idx = buffer.First( rePrompt.GetMatch( buffer ) );
    cbAssert( idx != wxNOT_FOUND );
    m_ProgramIsStopped = true;
    m_QueueBusy = false;
    DebuggerCmd* cmd = CurrentCommand();
    if( cmd ) {
      RemoveTopCommand( false );
      buffer.Remove( idx );
      if( buffer[buffer.Length() - 1] == _T( '\n' ) ) {
        buffer.Remove( buffer.Length() - 1 );
      }
      cmd->ParseOutput( buffer.Left( idx ) );
      delete cmd;
      RunQueue();
    }
  } else {
    return;
  }
  bool notifyChange = false;
  wxArrayString lines = GetArrayFromString( buffer, _T( '\n' ) );
  for( unsigned int i = 0; i < lines.GetCount(); ++i ) {
    if( lines[i].StartsWith( _T( "Cannot execute " ) ) ) {
      Log( lines[i] );
    } else if( lines[i].Contains( _T( "Access violation" ) ) ) {
      m_ProgramIsStopped = true;
      Log( lines[i] );
      m_pDBG->BringCBToFront();
      Manager::Get()->GetDebuggerManager()->ShowBacktraceDialog();
      DoBacktrace( true );
      InfoWindow::Tip( lines[i], _T( "Access violation" ) );
      break;
    } else if( notifyChange ) {
      continue;
    } else if( reBP.Matches( lines[i] ) ) {
      m_ProgramIsStopped = true;
      Log( lines[i] );
      m_pDBG->BringCBToFront();
      Manager::Get()->GetDebuggerManager()->ShowBacktraceDialog();
      DoBacktrace( true );
      break;
    } else if( lines[i].Contains( _T( "Break instruction exception" ) ) && !m_pDBG->IsTemporaryBreak() ) {
      m_ProgramIsStopped = true;
      m_pDBG->BringCBToFront();
      Manager::Get()->GetDebuggerManager()->ShowBacktraceDialog();
      DoBacktrace( true );
      break;
    }
  }
  if( notifyChange ) {
    NotifyCursorChanged();
  }
  buffer.Clear();
}

bool CDB_driver::IsDebuggingStarted() const {
  return m_IsStarted;
}
